#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <functional>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <mysql/mysql.h>
using namespace std;

const string host = "106.54.51.147";
const string user = "cr";
const string passwd = "20040712";
const string db = "conndb";
const unsigned int port = 3306;

#define defalut_thread_nums 5
using task_t = function<void()>; // 任务类型

class Dbp
{
public:
    Dbp(int nums = defalut_thread_nums)
        : _stop(false)
    {
        InitMysql(); // 数据库初始化
        ConnMysql(); // 进行数据库连接
        for (int i = 0; i < nums; i++)
        {
            _vt.emplace_back(&Dbp::start, this); // 临时构造，不会调用拷贝构造(线程不支持赋值&&拷贝)
        }
    }

    // push可以接受任何类型的参数
    template <typename F, typename... Args>
    auto Push(F &&func, Args &&...args) -> future<decltype(func(args...))>
    {
        // 1.获取任务执行函数的返回值类型
        using ret_type = decltype(func(args...));
        // 2.参数绑定成task同类型(完美转发)
        function<ret_type(void)> f = bind(forward<F>(func), forward<Args>(args)...);
        // 3.packaged进行任务包装(使用智能指针，防止出作用域就销毁)
        auto pck = make_shared<packaged_task<ret_type(void)>>(f);
        future<ret_type> fut = pck->get_future();

        // 4.将包装好的函数放进任务队列
        {
            unique_lock<mutex> lck(_mtx);
            // push的类型是task_t,所以要对pck的类型进行lambda表达式转换
            _qtask.push([pck]()
                        { (*pck)(); });
            // 推送一个任务最好就唤醒一个线程。如果唤醒所有线程的话:
            // 所有线程就会重新在wait处进行判断，从而会增加cpu对线程的无效切换
            _cond.notify_one();
        }

        return fut;
    }
    void Stop()
    {
        // 不能上锁，如果上锁以后去唤醒其他线程就会造成死锁
        if (_stop == true)
            return; // 防止重复唤醒
        _stop = true;
        // 必须唤醒所有线程，并重新进入判断
        _cond.notify_all();

        // 等待线程退出
        for (auto &thread : _vt)
        {
            thread.join();
            cout << "goodby~" << endl;
        }
    }
    ~Dbp()
    {
        Stop();
        mysql_close(_msq);
    }

private:
    void start()
    {
        while (true)
        {
            task_t t;
            {
                unique_lock<mutex> lck(_mtx);
                // 任务为空时进行条件等待
                _cond.wait(lck, [this]()
                           { return !_qtask.empty() || _stop; });
                if (_stop) // 结束线程
                    return;
                t = _qtask.front();
                _qtask.pop();
            }
            // 释放锁
            t(); // 执行任务处理(线程自己独立去执行,不要影响其他线程获取任务)
        }
    }

    void InitMysql()
    {
        // 1.初始化
        // 用于存储有关数据库连接的各种信息，如连接状态、错误信息、查询结果等
        _msq = mysql_init(nullptr);
        if (nullptr == _msq)
        {
            cerr << "mysql_init failed..." << endl;
            return;
        }
        // 1.1设置默认字符集
        mysql_set_character_set(_msq, "utf8");
    }
    void ConnMysql()
    {
        // 2.连接数据库
        if (nullptr == mysql_real_connect(_msq, host.c_str(), user.c_str(), passwd.c_str(),
                                          db.c_str(), port, nullptr, 0))
        {
            cerr << "mysql_real_connect failed..." << endl;
            return;
        }
        cout << "连接数据库成功..." << endl;
    }

public:
    MYSQL *_msq; // 数据库句柄
private:
    vector<thread> _vt; // 线程管理
    // 实现同步互斥
    mutex _mtx;
    condition_variable _cond;
    queue<task_t> _qtask; // 任务队列
    bool _stop;           // 终止线程池标记符
};
